UserScript - TweetEmbed
script.js
新增至個人頁面
code:script.js
import {convertWholeText} from '/api/code/1cc/UserScript_-_TweetEmbed/convert.js';
export function insertText({text}) {
const cursor = document.getElementById('text-input');
cursor.focus();
const start = cursor.selectionStart; // in this case maybe 0
cursor.setRangeText(text);
cursor.selectionStart = cursor.selectionEnd = start + text.length;
const uiEvent = document.createEvent('UIEvent');
uiEvent.initEvent('input', true, false);
cursor.dispatchEvent(uiEvent);
}
scrapbox.PopupMenu.addButton({
title: 'Embed a tweet',
onClick: text => {
// URLがなければ何もしない
if (!/https:\/\/((twitter)|x)\.com\S+\/status\/\d+/.test(text)) return;
const cursor = document.getElementById('text-input')
Promise.all(text.split('\n').map(line => {
const matches = line.match(/^\s+|.*/g);
const indent = /^\s+$/.test(matches0)? matches0 : ''; const content = /^\s+$/.test(matches0)? matches1 : matches0; return convertWholeText(content, indent)
})).then(lines => insertText({text: lines.join('\n'), cursor: cursor}));
// 入力しやすいよう選択範囲を先に消しておく
return '';
},
});
convert.js
code:convert.js
import {getTweetInfo} from '/api/code/1cc/UserScript_-_TweetEmbed/getTweetInfo.js';
// 複数のURLを含んだテキストをまとめて変換する
export async function convertWholeText(text,indent) {
const tweetRegExp = /https:\/\/((twitter)|x)\.com\S+\/status\/\d+(?:\?s=\d+)?/g;
const urls = text.match(tweetRegExp) ?? [];
if (urls.length === 0) return undefined;
const tweets = (await Promise.all(urls.map(url => getTweetInfo({tweetUrl: url}))))
.map(tweetInfo => {
console.log(tweetInfo)
return convert({
tweetInfo,
indent,
})
});
let map = {};
for (const originalUrl of urls) {
const i = urls.indexOf(originalUrl);
}
//console.log(map);
const result = text.replace(tweetRegExp, match => mapmatch ?? match); //console.log(result);
return result;
}
決定轉換後的文字格式
code:convert.js
function convert({tweetInfo, indent}) {
return [
...tweetInfo.content.map((text, i) => {
// 先頭だけに名前をつける
if(i===0){
return ${indent}>[@${tweetInfo.author.screenName} ${tweetInfo.date.href}]: ${text}
} else {
return ${indent}>${text}
}
})
].join('\n');
}
getTweetInfo.js
code:getTweetInfo.js
export async function getTweetInfo({tweetUrl} = {}) {
if (!window.GM_fetch) {
alert('Please install GM_fetch');
return;
}
const getHighestResolutionVideoSrc = (json) => {
const variants = json.variants;
let highestResolution = -1;
let highestResolutionVideoSrc = null;
for (const variant of variants) {
if (variant.type === 'video/mp4') {
let resolution;
let width, height;
if (/\d+x\d+/.test(variant.src)) {
// URLに解像度情報が含まれている(長い動画)の場合それを利用する
} else if (json.aspectRatio) {
// 短い動画の場合この要素に解像度情報が含まれている
} else {
continue;
}
resolution = width * height;
if (resolution > highestResolution) {
highestResolution = resolution;
highestResolutionVideoSrc = variant.src;
}
}
}
return highestResolutionVideoSrc;
}
const getTweetInfo = async (id, lang = "en") => {
const params = new URLSearchParams([
// 不要だった
]);
const res = await GM_fetch(https://cdn.syndication.twimg.com/tweet-result?${params}, {
headers: {
"content-type": "application/json; charset=utf-8",
},
});
return await res.json();
};
const tweetIdRegex = /\/status\/(\d+)/;
const tweetIdMatch = tweetUrl.match(tweetIdRegex);
const tweetId = tweetIdMatch ? tweetIdMatch1 : null; const removeTcoUrls = (text) => {
const tcoUrlRegex = /https?:\/\/t\.co\/\S+/g;
return text.replace(tcoUrlRegex, '').trim();
}
try {
const tweet = await getTweetInfo(${tweetId})
// textを整形
const lines = [];
const text = tweet.text
// URLの置換
const replacedText = tweet.entities.urls.reduce((acc, urlObj) => {
return acc.replace(urlObj.url, urlObj.expanded_url);
}, text);
lines.push(removeTcoUrls(replacedText))
// TODO: Gyazoにアップロードする
if(tweet.photos) {
lines.push(tweet.photos.map(u => [${u.url}]).join('\n'))
}
if(tweet.video) {
const highestResolutionVideoSrc = getHighestResolutionVideoSrc(tweet.video)
lines.push([${highestResolutionVideoSrc}#.mp4])
}
// 各種情報を詰め込んで返す
const user = tweet.user;
return {
author: {
name: user.name,
screenName: user.screen_name,
},
content: lines.join('\n').split('\n'),
date : {
href: tweetUrl,
// "2023-03-19T12:41:58.000Z" => '2023-03-19 12:41:58'
text: tweet.created_at.replace("T", " ").split(".")0 },
};
} catch(e) {
console.error(e);
}
}